package net.cathackers.devel.scmp.chat.impl;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import org.apache.log4j.Logger;
import org.dom4j.Document;

import net.cathackers.devel.exceptions.CATException;
import net.cathackers.devel.scmp.auth.interfaces.IdentityManager;
import net.cathackers.devel.scmp.auth.interfaces.UserID;
import net.cathackers.devel.scmp.auth.interfaces.UserProfile;
import net.cathackers.devel.scmp.chat.factories.ChatServiceFactory;
import net.cathackers.devel.scmp.chat.impl.xmpp.messageComponent.MessageComponent;
import net.cathackers.devel.scmp.chat.impl.xmpp.presence.PresenceComponent;
import net.cathackers.devel.scmp.chat.impl.xmpp.presence.PresenceHandler;
import net.cathackers.devel.scmp.chat.interfaces.ChatSession;
import net.cathackers.devel.scmp.chat.interfaces.ChatConfiguration;
import net.cathackers.devel.scmp.chat.interfaces.XMPPChatManager;
import net.cathackers.devel.scmp.chat.interfaces.ManagedQueue;
import net.cathackers.devel.scmp.chat.interfaces.OnlineSessionsCache;
import net.cathackers.devel.scmp.chat.interfaces.Protocol;
import net.cathackers.devel.scmp.chat.interfaces.XMPPConnection;
import net.cathackers.devel.scmp.chat.interfaces.sockets.SocketServerMonitor;
import net.cathackers.devel.scmp.chat.interfaces.sockets.SocketServer;
import net.cathackers.devel.scmp.factories.SCMPFactory;
import net.cathackers.devel.scmp.interfaces.CoreSwitch;
import net.cathackers.devel.scmp.interfaces.Envelope;
import net.cathackers.devel.scmp.xmpp.IQPacket;
import net.cathackers.devel.scmp.xmpp.MessagePacket;
import net.cathackers.devel.scmp.xmpp.Packet;
import net.cathackers.devel.scmp.xmpp.PacketRegistery;
import net.cathackers.devel.scmp.xmpp.PresencePacket;
import net.cathackers.devel.scmp.xmpp.RosterPacket;
import net.cathackers.devel.scmp.xmpp.PresencePacket.PresenceType;
import net.cathackers.devel.scmp.xmpp.exceptions.UnknownPacket;

public class XMPPChatManagerImpl extends XMPPChatManager {
    OnlineSessionsCache onlineSessions_;
    Hashtable<UserID, Set<UserID>> subscribersCache_;
    IdentityManager identityManager_;
    // Input Queues
    ManagedQueue<Packet> iqQueue_;
    ManagedQueue<Packet> messagesQueue_;
    ManagedQueue<Packet> presenceQueue_;
    // Output Queue
    ManagedQueue<Packet> outputQueue_;

    SocketServer socketServer_;
    SocketServerMonitor socketServerMonitor_;
    ChatConfiguration config_;
    CoreSwitch coreSwitch_;
    Logger logger_;

    public XMPPChatManagerImpl() {
	coreSwitch_ = SCMPFactory.getCoreSwitch();
	config_ = SCMPFactory.getConfiguration();
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#shutdown()
     */
    @Override
    public void shutdown() {

	socketServerMonitor_.stopMonitor();
	logger_.info("Close was requested to the socket server monitor.");
	logger_
		.info("Waiting for SocketServerMonitor to shutdown, at most 5 seconds");
	try {
	    socketServerMonitor_.join(5000);
	} catch (InterruptedException e) {
	    logger_
		    .error("SocketServerMonitor was interrupted during shutdown!");
	}

	socketServer_.stopServer();
	logger_.info("Close was requested to the socket server.");
	logger_.info("Waiting for SocketServer to shutdown, at most 5 seconds");

	// notifyUsers();
	// flushAllCaches();
	// doCleanUp(); // Close Database, maybe?

    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#handle(net.cathackers.devel.scmp.interfaces.Envelope)
     */
    @Override
    public void handle(Envelope envelope) throws CATException {
	// TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#initialize()
     */
    @Override
    public void initialize() throws CATException {
	onlineSessions_ = ChatServiceFactory.getOnlineSessionsCache();
	subscribersCache_    = new Hashtable<UserID, Set<UserID>>();
	identityManager_ = SCMPFactory.getIdentityManager();
	logger_ = SCMPFactory.getLogger(this);
	socketServer_ = ChatServiceFactory.newSocketServer(Protocol.Jabber,
		this);
	socketServerMonitor_ = ChatServiceFactory
		.newSocketServerMonitor(socketServer_);

	try {
	    socketServer_.start();
	} catch (IOException e) {
	    e.printStackTrace();
	}
	outputQueue_ = new ManagedQueueImpl<Packet>(
		new OnlineDeliveryProcessor(), 10);
	// socketServerMonitor_.start();
	iqQueue_ = new ManagedQueueImpl<Packet>(ChatServiceFactory.getIQComponent(), 10);
	presenceQueue_ = new ManagedQueueImpl<Packet>(
		new PresenceComponent(new PresenceHandler(SCMPFactory
			.getRosterSubscriptionManager())), 10);
	messagesQueue_ = new ManagedQueueImpl<Packet>(new MessageComponent(
		SCMPFactory.getOfflineMessageStore()), 10);
	logger_.info("SCMP Chat server started and bound on domain "
		+ config_.getChatServerDomain());

    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#registerNewServiceManagerCallBack()
     */
    @Override
    public void registerNewServiceManagerCallBack() {
	// TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#unregisterServiceManagerCallBack()
     */
    @Override
    public void unregisterServiceManagerCallBack() {
	// TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#getUserProfile(net.cathackers.devel.scmp.auth.interfaces.UserID)
     */
    @Override
    public UserProfile getUserProfile(UserID userID) {
	return identityManager_.getProfile(userID.getID());
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#generateSessionID()
     */
    @Override
    public String generateSessionID() {
	boolean found = false;
	String ret = null;
	while (!found) {
	    String key = generateRandomString(10);
	    if (onlineSessions_.checkIfSessionIDExists(key)) {
		continue;
	    } else {
		ret = key;
		found = true;
	    }
	}
	return ret;
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#unregisterSession(net.cathackers.devel.scmp.chat.interfaces.ChatSession)
     */
    @Override
    public boolean unregisterSession(ChatSession session) {
	boolean ret = false;
	ret = this.destroySession(session.getSessionID());
	return ret;
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#destroySession(java.lang.String)
     */
    @Override
    public boolean destroySession(String sessionID) {
	boolean ret = false;
	logger_.info("Destroying session " + sessionID);
	ret = onlineSessions_.removeSession(sessionID);
	logger_.debug(ret);
	if (ret == false) {
	    logger_.warn("session not removed");
	} else {
	    logger_.debug("Session with sessionID " + sessionID
		    + " has been removed");
	}
	return ret;
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#createSession(java.lang.String, org.dom4j.Document, net.cathackers.devel.scmp.chat.interfaces.XMPPConnection)
     */
    @Override
    public ChatSession createSession(String serverName, Document initDoc,
	    XMPPConnection connection) {
	ChatSession session = ClientChatSession.getInstance(serverName,
		initDoc, connection);
	logger_.debug("Creating Session for connection with SessionID "
		+ session.getSessionID());
	this.registerSession(session);
	return session;
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#deliverToUser(net.cathackers.devel.scmp.xmpp.Packet)
     */
    @Override
    public void deliverToUser(Packet packet) {
	outputQueue_.enqueue(packet);
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#route(net.cathackers.devel.scmp.xmpp.Packet)
     */
    @Override
    public void route(Packet packet) {
	/*
	 * there is a trick here, we will enqueue the presence packet in the IQ
	 * component if its type is roster-related to make sure that we process
	 * packets in order.
	 */
	boolean isIQ = false;
	if (packet instanceof PresencePacket) {
	    PresencePacket pack = (PresencePacket) packet;
	    PresenceType currentType = pack.getPresenceType();
	    switch (currentType) {
	    case SUBSCRIBE:
	    case SUBSCRIBED:
	    case UNSUBSCRIBE:
	    case UNSUBSCRIBED:
		isIQ = true;
		logger_.debug("Presence packet will enqueued in the IQ queue");
	    }
	    if (!isIQ) {
		logger_.debug("packet going into presence queue");
		presenceQueue_.enqueue(packet);
	    }
	}

	if (isIQ || packet instanceof IQPacket || packet instanceof RosterPacket) {
	    logger_.debug("packet going into IQ queue");
	    iqQueue_.enqueue(packet);
	}

	if (packet instanceof MessagePacket) {
	    logger_.debug("packet going into messages queue");
	    messagesQueue_.enqueue(packet);
	}

    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#route(org.dom4j.Document, java.lang.String)
     */
    @Override
    public void route(Document doc, String sessionID) throws UnknownPacket {
	try {
	    logger_.debug("route the document to the suitable component");
	    Packet packet = PacketRegistery.createPacket(doc);
	    logger_.debug("create new packet" + packet.toXML());
	    ChatSession chatSession = getChatSession(sessionID);
	    if (chatSession != null && chatSession.isAuthenticated()) {
		    logger_.debug("Setting the packet to be from: "
			    + chatSession.getUserID().getCanonicalName());
		    packet.setFrom(chatSession.getUserID());
	    }
	    packet.setBaseStreamID(sessionID);
	    logger_.debug("routing the packet");
	    route(packet);
	} catch (UnknownPacket ex) {
	    logger_.warn("Unknown Packet was received: " + ex.getMessage());
	    logger_.warn("Packet dump: " + doc.asXML());
	    throw ex;
	}

    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#registerSession(net.cathackers.devel.scmp.chat.interfaces.ChatSession)
     */
    @Override
    public void registerSession(ChatSession session) {
	if (session != null) {
	    onlineSessions_.addSession(session);
	    logger_.debug("Session " + session.getSessionID()
		    + "was added to online sessions cache");
	}
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#getChatSession(net.cathackers.devel.scmp.auth.interfaces.UserID)
     */
    @Override
    public ChatSession[] getChatSession(UserID userID) {
	return onlineSessions_.getSession(userID);
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#getChatSession(java.lang.String)
     */
    @Override
    public ChatSession getChatSession(String sessionID) {
	return onlineSessions_.getSessionBySessionID(sessionID);
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#checkIfSessionOnline(net.cathackers.devel.scmp.auth.interfaces.UserID)
     */
    @Override
    public boolean checkIfSessionOnline(UserID userID) {
	return onlineSessions_.checkIfUserIDExists(userID);
    }

    /* (non-Javadoc)
     * @see net.cathackers.devel.scmp.chat.impl.ChatManager#identifySession(java.lang.String)
     */
    @Override
    public void identifySession(String sessionID) {
	onlineSessions_.identifySession(sessionID);
    }
    
    @Override
    public void addSubscriber(UserID owner, UserID subscriber) {
	Set<UserID> subscribersSet = subscribersCache_.get(owner);
	if(subscribersSet != null) {
	    logger_.debug("adding a subscriber of " + owner.getID());
	    subscribersSet.add(subscriber);
	} else {
	    logger_.debug("create new subscribers set of " + owner.getID());
	    subscribersSet = Collections.synchronizedSet(new HashSet<UserID>());
	    subscribersSet.add(subscriber);
	    subscribersCache_.put(owner, subscribersSet);
	}
    }
    
    @Override
    public void removeSubscriber(UserID owner, UserID subscriber) {
	logger_.debug("removing a subscriber of " + owner.getID());
	Set<UserID> subscribersSet = subscribersCache_.get(owner);
	if(subscribersSet != null)
	subscribersSet.remove(subscriber);
    }
    
    @Override
    public Set<UserID> getSubscribers(UserID owner) {
	Set<UserID> subscribersSet = subscribersCache_.get(owner);
	logger_.debug("getting the online subscribers of " + owner.getID());
	return subscribersSet;
    }
    
    @Override
    public void removeSubscribersCacheElement(UserID owner) {
	logger_.debug("removing this element " + owner.getID());
        subscribersCache_.remove(owner);
	
    }

}
